<?php

/**
 * Framework_DB_DaoNode
 *
 * @author Application Support Group
 * @copyright University of Georgia
 * @package BAMF
 * @filesource
 * 
 */

abstract class Framework_DB_DaoNode extends Framework_DB {

	const DAO_TYPE = 'DaoNode' ;
	
	/**
	 * NULL_TYPES
	 * Some datatypes break really badly if you put an empty string or whatever
	 * in them, so we keep track of them in this array and when we encounter them
	 * we make sure to use a NULL instead of an empty string.
	 */
	protected $_NULL_TYPES = array(
			DB_DATATYPE_DATE, 
			DB_DATATYPE_DATETIME, 
			DB_DATATYPE_INTEGER,
			DB_DATATYPE_KEY,
			DB_DATATYPE_FLOAT,
			DB_DATATYPE_TIMESTAMP,
			DB_DATATYPE_CURRENCY,
		);

	/**
	 * $_DB_TABLE
	 * @var string $_DB_TABLE The database table name
	 */
	protected $_DB_TABLE ;

	/**
	 * $_PK_FIELD
	 * @var string $_PK_FIELD The primary key field name
	 */
	protected $_PK_FIELD ;

	/**
	 * $_attributes
	 * @var array $_attributes
	 */
	protected $_attributes ;

	/**
	 * $_parent
	 * @var type $_parent The parent node object.
	 */
	protected $_parent ;

	/**
	 * $_children
	 * @var type $_children Collection of child nodes.
	 */
	protected $_children ;

	/**
	 * $_schema
	 * @var array $_schema Node schema as defined in .ini file.
	 */
	public $_schema ;

	/**
	 * __construct
	 * 
	 * @author Application Support Group
	 * @param $schemaFile string Path to schema file
	 * @return void
	 * @access public 
	 */
	public function __construct( $schemaFile ) {

		$this->_schema = file_exists( $schemaFile ) ? 
			parse_ini_file( $schemaFile, true ) : 
				Framework_ErrorHandler::raiseError( "No database schema file found for class: Tried \"$schemaFile\"", 
						__METHOD__."::".__LINE__ 
						) ;

		if( Framework_ErrorHandler::isError( $this->_schema, __METHOD__."::".__LINE__ )) {
			Framework_Util_Debug::show( array( __METHOD__, $this->_schema->getMessage() )) ;
		} else {
			if ( isset( $this->_schema['db_parameters']['schema'] )) {
				$this->_DB_TABLE = $this->_schema['db_parameters']['schema'] . '.' . $this->_schema['db_parameters']['table'] ;
			} else {
				$this->_DB_TABLE .= $this->_schema['db_parameters']['table'] ;
			}

			if ( isset( $this->_schema['db_parameters']['dsn'] )) {
				$theDsn = $this->_schema['db_parameters']['dsn'] ;
			} else {
				$theDsn = FRAMEWORK_DSN ;
			}

			$this->_PK_FIELD = $this->_schema['db_parameters']['pkfield'] ;
			$this->_attributes = $this->_schema['field_default_values'] ;
		}
		$attribute_count = count($this->_attributes);
		if($attribute_count != count($this->_schema['fields']) ||
				$attribute_count != count($this->_schema['field_definitions']) ||
				$attribute_count != count($this->_schema['field_aliases']) ||
				$attribute_count != count($this->_schema['field_default_values']) ||
				$attribute_count != count($this->_schema['field_values_required'])) {
			trigger_error('Invalid schema file for ' . $this->getClass() . ': field count mismatch.<br>Check the numeric indices under [fields] for duplicates.<br>', E_USER_ERROR) ;
		}

		parent::__construct( $theDsn ) ;

	}// end constructor




	/**
	 * __get
	 * 
	 * @author Application Support Group
	 * @param $anAttribute string An attribute name.
	 * @return mixed Attribute value on success, PEAR error on failure
	 * @access public 
	 */
	public function __get( $anAttribute ) {

		$_output = false ;

		if( array_key_exists( $anAttribute, $this->_attributes )) {
			$_output = $this->_attributes[$anAttribute] ;
		} else {
			if ( SHOW_DEBUG ) {
				$_output = Framework_ErrorHandler::raiseError( 
						"Cannot get the requested property [$anAttribute] .", 
						__METHOD__."::".__LINE__ 
						) ;
				trigger_error("Cannot get the requested property [$anAttribute] .", E_USER_WARNING) ;
			}
		}

		return $_output ;
	}




	/**
	 * __set
	 * 
	 * @author Application Support Group
	 * @param $anAttribute string An attribute name.
	 * @param $aValue mixed An attribute value.
	 * @return mixed Returns true on success, PEAR error on failure
	 * @access public 
	 */
	public function __set( $anAttribute, $aValue ) {

		$_output = false ;

		if( array_key_exists( $anAttribute, $this->_attributes )) {
			$this->_attributes[$anAttribute] = $aValue ;
			$_output = true ;
		} else {
			if ( SHOW_DEBUG ) {
				$_output = Framework_ErrorHandler::raiseError( 
						"Cannot set the requested property [$anAttribute] .", 
						__METHOD__."::".__LINE__ 
						) ;
			}
		}

		return $_output ;
	}



	/**
	 * getParent
	 * 
	 * @author Application Support Group
	 * @return DaoNode Returns parent DaoNode
	 * @access public 
	 */
	public function getParent() {
		return $this->_parent ;
	}// end getParent() 



	/**
	 * __setParent
	 * 
	 * @author Application Support Group
	 * @param $aParentNode DaoNode A DaoNode object
	 * @return mixed Returns true on success, PEAR error on failure.
	 * @access public 
	 */
	public function setParent( $aParentNode ) {

		$_output = false ;

		if( $aParentNode instanceof Framework_DB_DaoNode ) {
			$this->_parent = $aParentNode ;
			$_output = true ;
		} elseif( $aParentNode == null ) {
			$this->_parent = $aParentNode ;
			$_output = true ;
		} else {
			$this->_parent = Framework_ErrorHandler::raiseError( 
					"Input object does not extend class Framework_DB_DaoNode", 
					__METHOD__."::".__LINE__ 
					) ;
			$_output = $this->_parent ;
		}

		return $_output ;

	}// end setParent




	/**
	 * getChildren
	 * 
	 * @author Application Support Group
	 * @return DaoCollection Returns DaoCollection
	 * @access public 
	 */
	public function getChildren() {
		return $this->_children ;
	}// end getChildren




	/**
	 * setChildren
	 * 
	 * @author Application Support Group
	 * @param $aValue DaoCollection A DaoCollection object
	 * @return mixed Returns true on success, PEAR error on failure.
	 * @access public 
	 */
	public function setChildren( $aValue ) {

		$_output = false ;

		if( $aValue instanceof Framework_DB_DaoCollection ) {
			$this->_children = $aValue ;
			$_output = true ;
		} elseif( $aValue == null ) {
			$this->_children = $aValue ;
			$_output = true ;
		} else {
			$this->_children = Framework_ErrorHandler::raiseError( 
					"Input object does not extend class Framework_DB_DaoCollection", 
					__METHOD__."::".__LINE__ 
					) ;
			$_output = $this->_children ;
		}

		return $_output ;

	}// end setChildren




	/**
	 * getSchemaFile
	 * 
	 * @author Application Support Group
	 * @access public 
	 */
	abstract public static function getSchemaFile() ;



	/**
	 * getSchema
	 * 
	 * @author Application Support Group
	 * @param $aSchemaFile string Path to schema file
	 * @return mixed Returns schema array on success, PEAR error on failure.
	 * @access public 
	 */
	public static function getSchema( $aSchemaFile ) {
		return file_exists( $aSchemaFile ) ? 
			parse_ini_file( $aSchemaFile, true ) : 
				Framework_ErrorHandler::raiseError( 
						"No database schema file found for class: Tried \"$aSchemaFile\"", 
						__METHOD__."::".__LINE__ 
						) ;
	}




	/**
	 * create
	 *
	 * Long Description...
	 *
	 * @param		boolean $doRefresh Refresh node after it's created
	 * @param		string	$validationMode Defaults to DEFAULT_VALIDATION_MODE
	 *
	 * @return		type description
	 *
	 * @package		package name
	 *
	 * @todo		Look into a better method of getting the primary key value from the last insert.
	 * 
	 */
	public function create( $doRefresh=false, $validationMode=DEFAULT_VALIDATION_MODE ) {

		$_output = false ;
		$isValid = false ;
		$thisDsn = $this->db->dsn['dbsyntax'] ;
		$idFromSequence = $thisDsn == DSN_OCI ? true : false ;
		$doRefresh = $thisDsn == DSN_OCI ? false : $doRefresh ;
		$sqlValues = array() ; 
		$sequence = "" ;

		$validation = $this->isValid( $validationMode ) ;

		if( !Framework_ErrorHandler::isError( $validation, __METHOD__."::".__LINE__ )) {
			$isValid = true ;
		} else {
			$_output = $validation ;
		}


		if( $isValid ) {

			$fieldArray = array() ;
			$fieldList = "" ;
			$valueArray = array() ;
			$valueList = "" ;

			// assemble key/value pairs
			foreach( $this->_attributes as $key => $value ) {
				if( $this->_schema['field_definitions'][$key] == DB_DATATYPE_VIRTUAL ) {
					continue;
				}

				// only non-primary keys here
				if( $key != $this->_PK_FIELD ) {

					// if the value is real or zero
					if (!empty($value) || (is_numeric($value))) {

						$fieldArray[] = $key ;

						// handle dates
						if( $this->_schema['field_definitions'][$key] == DB_DATATYPE_DATETIME &&
								$thisDsn == DSN_OCI ) {
							$valueArray[] = "TO_DATE( ?, '".OCI_DATE_FORMAT_STRING."' )" ;
							$sqlValues[] = date( OCI_DATE_FORMAT, strtotime( $value )) ;
						} else {
							$valueArray[] = "?" ;
							$sqlValues[] = $value ;
						}

						// add the schema default value if it exists
					} else {
						$fieldArray[] = $key ;
						$valueArray[] = "?" ;
						//Some datatypes don't respond well to getting an empty string for the default value.
						if($this->_schema["field_default_values"][$key] === '' && 
								in_array($this->_schema["field_definitions"][$key], $this->_NULL_TYPES)) {
							//so we'll use null instead
							$sqlValues[] = null;
						} else {
							$sqlValues[] = $this->_schema["field_default_values"][$key] ;
						}
					}

					// handle primary keys (Oracle)
				} else {
					if( $thisDsn == DSN_OCI ) {
						$fieldArray[] = $key ;
						$valueArray[] = "?" ;

						if ( FRAMEWORK_DB_SCHEMA ) $sequence = FRAMEWORK_DB_SCHEMA . "." ;
						$sequence .= FRAMEWORK_OCI_SEQUENCE_NAME ;

						$newId = $this->db->nextId( $sequence ) ;
						$sqlValues[] = $newId ;
						$this->{$this->_PK_FIELD} = $newId ;
					}
				}





			}// end foreach

			$fieldList = "(" . implode(",", $fieldArray) . ")" ;
			$valueList = "(" . implode(",", $valueArray) . ")" ;
			$sqlStatement = "INSERT INTO {$this->_DB_TABLE} $fieldList VALUES $valueList" ;
			$preparedStmt = $this->db->prepare( $sqlStatement ) ;
			if(Framework_ErrorHandler::isError($preparedStmt)) {
				return $preparedStmt;
			}
			$result = $preparedStmt->execute( $sqlValues ) ;
			$preparedStmt->free() ;

			if( Framework_ErrorHandler::isError( $result, __METHOD__."::".__LINE__ )) {
				$_output = $result ;
			} else {
				//
				//	TODO
				//	Need a better means to get the ID of the last insert:
				//	there is no guarantee that DB::autoExecute() called 
				//	above will be the same record (ID) returned by 
				//	DB::getOne()
				//		
				//	NOTE: This seems not to be a problem for OCI sources	
				//	as we're using a sequence to generate the ID in code	
				//		
				//	UPDATE 2007.07.16: This *might* be taken care of now 
				//	of using MDB2::lastInsertID()
				//		
				if( $doRefresh ) { 
					$success = $this->refresh() ; 

					if( !Framework_ErrorHandler::isError( $success, __METHOD__."::".__LINE__ )) {
						$_output = $this->db->lastInsertID( $this->_DB_TABLE ) ;
					} else {
						$_output = $success ;
					}

				} elseif( $idFromSequence ) { 
					$_output = $this->{$this->_PK_FIELD} ; 
				} else { 
					$_output = $this->db->lastInsertID( $this->_DB_TABLE ) ;
				}
			}
		}// end if

		return $_output ;

	}// end create



	/**
	 * Refresh
	 *
	 * Alias to retrieve
	 *
	 * @return True or PEAR_Error
	 */
	public function refresh() {
		return $this->retrieve() ;
	}



	/**
	 * Retrieve
	 *
	 * Return data from DB
	 *
	 * @param	$primaryKeyValue	The Primary Key Value of the record to retrieve.  Defaults to the existing value.
	 * @return	Mixed: true or PEAR_Error
	 */
	public function retrieve( $primaryKeyValue=null ) {
		$_output = false ;

		$pkv = !empty( $primaryKeyValue ) && is_numeric( $primaryKeyValue ) ? $primaryKeyValue : $this->{$this->_PK_FIELD} ;
		$fields = "" ;
		$fieldCount = count( $this->_schema['fields'] ) ;
		for( $i=0; $i<$fieldCount; $i++ ) {
			$field = $this->_schema['fields'][$i] ;
			if( $this->_schema['field_definitions'][$field] == DB_DATATYPE_VIRTUAL ) {
				continue ;
			}

			switch( $this->db->dsn['dbsyntax'] ) {

				// handle oracle dates
				case DSN_OCI :
					if( $this->_schema['field_definitions'][$field] == DB_DATATYPE_DATETIME ) {
						$fields .= "TO_CHAR( $field, '".OCI_DATE_FORMAT_STRING."' ) AS $field" ;
					} else {
						$fields .= $field ;
					}

					break ;


				case DSN_MSSQL :
				case DSN_MYSQL :
				default :
					$fields .= $field ;
			}// end switch


			if( $i < ($fieldCount - 1) ) {
				$fields .= "," ;
			}
		}
		//if the last datatype is DB_DATATYPE_VIRTUAL there will be a , at the end, which shouldn't be there.
		$fields = rtrim($fields, ',');
		$sql = "SELECT $fields FROM ".$this->_DB_TABLE." WHERE ".$this->_PK_FIELD."=$pkv" ;

		$result = $this->db->query( $sql ) ;

		if( Framework_ErrorHandler::isError( $result, __METHOD__."::".__LINE__ )) {
			$_output = $result ;
		} else {
			while( $nextRecord = $result->fetchRow()) {

				if( !Framework_ErrorHandler::isError( $nextRecord, __METHOD__."::".__LINE__ )) {
					$keys = array_keys( $nextRecord ) ;

					for( $i=0; $i<count($keys); $i++ ) {
						$nextKey = $keys[$i] ;
						if (!empty($nextRecord[$nextKey]) || is_numeric($nextRecord[$nextKey])) {
							$this->$nextKey = $nextRecord[$nextKey] ;
						}
					}

					$_output = true ;

				} else {
					$_output = $nextRecord ;
					break ;
				}
			}

			$result->free() ;
		}

		return $_output ;

	}// end retrieve



	/**
	 * update
	 *
	 * Save the object's values to the DB
	 *
	 * @param	$doRefresh	Refresh the object's data after saving; defaults to true.
	 * @param	$validationMode	The validation mode; defaults to the application's DEFAULT_VALIDATION_MODE
	 *
	 * @return	Mixed: true or PEAR_Error
	 */
	public function update( $doRefresh=true, $validationMode = DEFAULT_VALIDATION_MODE ) {

		$_output = false ;
		$isValid = false ;
		$sqlValues = array() ; // just to see some sql


		$validation = $this->isValid($validationMode) ;

		if( !Framework_ErrorHandler::isError( $validation, __METHOD__."::".__LINE__ )) {
			$isValid = true ;
		} else {
			$_output = $validation ;
		}



		if( $isValid ) {

			$setValueArray = array() ;
			$setValueList = "" ;
			$thisDsn = $this->db->dsn['dbsyntax'] ;

			// assemble key/value pairs
			foreach( $this->_attributes as $key => $value ) {

				if( $this->_schema['field_definitions'][$key] == DB_DATATYPE_VIRTUAL ) {
					continue;
				}


				if( $key != $this->_PK_FIELD) {
					// handle dates
					if( ($value === '') && in_array($this->_schema["field_definitions"][$key],
								$this->_NULL_TYPES)) {
						$setValueArray[] = "$key=?" ;
						$sqlValues[] = null ;
					} else if( $this->_schema['field_definitions'][$key] == DB_DATATYPE_DATETIME &&
							$thisDsn == DSN_OCI ) {
						$setValueArray[] = "$key=TO_DATE( ?, '".OCI_DATE_FORMAT_STRING."' )" ;
						$sqlValues[] = date( OCI_DATE_FORMAT, strtotime( $value )) ;
					} else {
						$setValueArray[] = "$key=?" ;
						$sqlValues[] = $value ;
					}
				}


			}// end foreach



			// execute the update
			$setValueList = implode( ",", $setValueArray );
			$sqlStatement = "UPDATE {$this->_DB_TABLE} SET $setValueList WHERE ".$this->_PK_FIELD."=".$this->{$this->_PK_FIELD} ;
			$preparedStmt = $this->db->prepare( $sqlStatement ) ;
			if(Framework_ErrorHandler::isError($preparedStmt)) {
				return $preparedStmt;
			}
			$result = $preparedStmt->execute( $sqlValues ) ;
			$preparedStmt->free() ;


			if( Framework_ErrorHandler::isError( $result, __METHOD__."::".__LINE__ )) {
				$_output = $result ;
			} else {
				if( $doRefresh ) { $_output = $this->refresh() ; }
				else { $_output = true ; }
			}

		}// end if

		return $_output ;

	}// end update



	/**
	 * remove
	 *
	 * Remove the record from the DB
	 *
	 * @param	$primaryKeyValue	The Primary Key Value of the record to retrieve.  Defaults to the existing value.
	 *
	 * @return	Mixed: true or PEAR_Error
	 */
	public function remove( $primaryKeyValue = null) {
		$_output = false ;

		$pkv = !empty( $primaryKeyValue ) && is_numeric( $primaryKeyValue ) ? $primaryKeyValue : $this->{$this->_PK_FIELD} ;
		$sql = "DELETE FROM ".$this->_DB_TABLE." WHERE ".$this->_PK_FIELD." = ".$pkv ;
		$result = $this->db->query( $sql ) ;

		if( Framework_ErrorHandler::isError( $result, __METHOD__."::".__LINE__ )) {
			$_output = $result ;
		} else {
			$_output = true ; 
		}

		return $_output ;
	}// end remove


	/**
	 * getRemoveStatement
	 *
	 * Return the SQL string that would remove this record from the DB.
	 *
	 * @param	$parameterized	If true, returns an array with a parameterized query at key "sql" and the values at key "values", else returns a string. Defalts to false.
	 * 
	 * @return	string
	 */
	public function getRemoveStatement($parameterized = false) {
		$pkf = $this->_schema['db_parameters']['pkfield'];

		if($parameterized) {
			return array('sql' => "DELETE FROM {$this->_DB_TABLE} WHERE {$pkf} = {$this->$pkf}", 
					'values' => array($this->$pkf));
		} else {
			return "DELETE FROM {$this->_DB_TABLE} WHERE {$pkf} = {$this->$pkf}" ;
		}
		return $sql ;
	}




	/**
	 * getCreateStatement
	 *
	 * Return the SQL string that would create this record in the DB.
	 *
	 * @param	$parameterized	If true, returns an array with a parameterized query at key "sql" and the values at key "values", else returns a string. Defalts to false.
	 *
	 * @return	string
	 */
	public function getCreateStatement($parameterized = false) {
		$valuesFragment = "" ;
		$valuesArray = array();
		$fieldCount = count( $this->_schema['fields'] ) ;

		for( $i=0; $i<$fieldCount; $i++ ) {
			$field = $this->_schema['fields'][$i] ;
			$fieldValue = ($parameterized ? '?' : $this->$field);
			if( $this->_schema['field_definitions'][$field] == DB_DATATYPE_VIRTUAL ) {
				continue ;
			}

			switch( $this->db->dsn['dbsyntax'] ) {

				case DSN_OCI :
					if( $this->_schema['field_definitions'][$field] == DB_DATATYPE_DATETIME ) {
						if($parameterized) {
							$valuesFragment .= " $field = TO_DATE( ? , ?) " ;
						} else {
							$valuesFragment .= " $field = TO_DATE( '$fieldValue', '" . OCI_DATE_FORMAT_STRING . "' ) " ;
						}
						$valuesArray[] = $this->$field;
						$valuesArray[] = $formatString;
					} else {
						$valuesFragment .= " {$this->$field} " ;
						$valuesFragment .= " $fieldValue " ;
						$valuesArray[] = $this->$field;
					}
					break ;

				case DSN_MSSQL :
				case DSN_MYSQL :
				default :
					if( !$parameterized && in_array($this->_schema['field_definitions'][$field], array(
							DB_DATATYPE_DATETIME, DB_DATATYPE_STRING_BASIC, DB_DATATYPE_STRING_EMAIL,
							DB_DATATYPE_STRING_PHONE, DB_DATATYPE_STRING_URI, DB_DATATYPE_STRING_XML,
							DB_DATATYPE_TIME))) {
						$valuesFragment .= " '$fieldValue' " ;
					} else {
						$valuesFragment .= " $fieldValue " ;
					}
					$valuesArray[] = $this->$field;
			}

			if( $i < ($fieldCount - 1) ) {
				$valuesFragment .= "," ;
			}
		}
		//if the last datatype is DB_DATATYPE_VIRTUAL there will be a , at the end, which shouldn't be there.
		$valuesFragment = rtrim($valuesFragment, ',');

		$sql = "INSERT INTO {$this->_DB_TABLE} VALUES ( $valuesFragment )" ;
		if($parameterized) {
			return array('sql' => $sql, 'values' => $valuesArray);
		} else {
			return $sql ;
		}
	}


	/**
	 * getUpdateStatement
	 *
	 * Return the SQL string that would updtate this record in the DB.
	 *
	 * @param	$parameterized	If true, returns an array with a parameterized query at key "sql" and the values at key "values", else returns a string. Defalts to false.
	 *
	 * @return	string
	 */
	public function getUpdateStatement($parameterized = false) {
		$pkf = $this->_schema['db_parameters']['pkField'];		
		$valuesFragment = "" ;
		$valuesArray = array();
		$fieldCount = count( $this->_schema['fields'] ) ;

		for( $i=0; $i<$fieldCount; $i++ ) {
			$field = $this->_schema['fields'][$i] ;
			$fieldValue = ($parameterized ? '?' : $this->$field);
			if( $this->_schema['field_definitions'][$field] == DB_DATATYPE_VIRTUAL ) {
				continue;
			}

			switch( $this->db->dsn['dbsyntax'] ) {

				case DSN_OCI :
					if( $this->_schema['field_definitions'][$field] == DB_DATATYPE_DATETIME ) {
						if($parameterized) {
							$valuesFragment .= " $field = TO_DATE( ? , ?) " ;
						} else {
							$valuesFragment .= " $field = TO_DATE( '$fieldValue', '" . OCI_DATE_FORMAT_STRING . "' ) " ;
						}
						$valuesArray[] = $this->$field;
						$valuesArray[] = $formatString;
					} else {
						$valuesFragment .= " {$this->$field} " ;
						$valuesFragment .= " $fieldValue " ;
						$valuesArray[] = $this->$field;
					}
					break ;

				case DSN_MSSQL :
				case DSN_MYSQL :
				default :
					if( !$parameterized && in_array($this->_schema['field_definitions'][$field], array(
							DB_DATATYPE_DATETIME, DB_DATATYPE_STRING_BASIC, DB_DATATYPE_STRING_EMAIL,
							DB_DATATYPE_STRING_PHONE, DB_DATATYPE_STRING_URI, DB_DATATYPE_STRING_XML,
							DB_DATATYPE_TIME))) {
						$valuesFragment .= " '$fieldValue' " ;
					} else {
						$valuesFragment .= " $fieldValue " ;
					}
					$valuesArray[] = $this->$field;
			}
			

			if( $i < ($fieldCount - 1) ) {
				$valuesFragment .= "," ;
			}
		}

		//if the last datatype is DB_DATATYPE_VIRTUAL there will be a , at the end, which shouldn't be there.
		$valuesFragment = rtrim($valuesFragment, ',');
		if($parameterized) {
			$sql = "UPDATE ".$this->_DB_TABLE.
				" SET ".$valuesFragment.
				" WHERE {$pkf} = ? ";
				$valuesArray[] = $this->$pkf;
				return array('sql' => $sql, 'values' => $valuesArray);
		} else {
			$sql = "UPDATE ".$this->_DB_TABLE.
				" SET ".$valuesFragment.
				" WHERE ".$pkf." = ".$this->$pkf ;
			return $sql ;
		}
	}



	/**
	 * isValid
	 *
	 * Validate the object's data
	 * @param	$validationMode	The validation mode to use; defaults to the application's DEFAULT_VALIDATION_MODE
	 * @return	mixed: true or PEAR_Error
	 */
	public function isValid($validationMode = DEFAULT_VALIDATION_MODE) {
		$_output = false ;
		$errors = "" ;
		$validationResults = Framework_DB_Validator::validate( $this, $validationMode ) ;

		foreach( $this->_attributes as $key => $value ) {
			if( Framework_ErrorHandler::isError( $validationResults[$key], __METHOD__."::".__LINE__ )) {
				$errors .= "\n" . $this->_schema['field_aliases'][$key] . " - " . $validationResults[$key]->getMessage() ;
			}
		}

		if( $validationResults["errorCount"] > 0 ) {

			if( !empty( $validationResults['objectError'] )) {
				$errors .= "\n". $validationResults['objectError']->getMessage() ;
			}

			$_output = Framework_ErrorHandler::raiseError( 
					"Validation Error:  The following validation errors were encountered:".$errors, 
					__METHOD__."::".__LINE__, 
					false 
					) ;
		} else {
			$_output = true ;
		}

		return $_output ;

	}// end isValid()



	/**
	 * toXml
	 *
	 * Return an XML representation of the object
	 *
	 * @param	$returnFragment	Whether to include the xml declaration, ie <code>&lt;xml version=&quot;1.0&quot; encoding=&quot;ISO-8859-1&quot; ?&gt;</code>
	 * @param	$encoding	The character encoding to use; defaults to <code>ISO-8859-1</code>
	 *
	 * @return	string
	 */
	public function toXml( $returnFragment=false, $encoding="ISO-8859-1" ) {
		$_output = "" ;

		if( !$returnFragment ) {
			$_output = "<?xml version='1.0' encoding='$encoding'?>" ;
		}

		$_output .= "<" . get_class( $this ) . ">" ;

		for( $i=0; $i<count( $this->_schema['fields'] ); $i++ ) {
			$nextField = $this->_schema['fields'][$i] ;
			$_output .= "<$nextField>" ;
			$_output .= $this->{$nextField} ;
			$_output .= "</$nextField>" ;
		}

		$_output .= "</" . get_class( $this ) . ">" ;
		return $_output ;
	}


	/**
	 * __toString()
	 *
	 * Return an String representation of the object.
	 *
	 * @return	string
	 */
	public function __toString() {
		$_output = __CLASS__ . "::" . get_class( $this ) ;

		// assemble key/value pairs
		foreach( $this->_attributes as $key => $value ) {
			$_output .= "\n\t$key => $value" ;
		}

		$_output .= "\n\n" ;

		return $_output ;
	}// end toString



	/**
	 * cloneNode
	 *
	 * Return a duplicate of the object.  The object returned will have all the same attribute values except for the primary key value, which will be null.
	 *
	 * @param	$aNode	The Framework_DB_DaoNode object to clone
	 *
	 * @return	object
	 */
	static public function cloneNode( Framework_DB_DaoNode $aNode ) {

		if ( $aNode instanceof Framework_DB_DaoNode ) {
			$nodeClass = get_class($aNode) ;

			$_output = new $nodeClass($aNode->{$aNode->_PK_FIELD} ) ;
			$_output->{$_output->_PK_FIELD} = null ;		

		} else {
			$_output = Framework_ErrorHandler::raiseError( 
				"Input object must be an instance of Framework_DB_DaoNode", 
				__METHOD__."::".__LINE__ 
				) ;
		}

		return $_output ;
	}


	/**
	 * equals
	 *
	 * Evaluate whether two node objects are the same.  Returns true if the nodes
	 * are instances of the same class and have equivalent $_attribute values.
	 *
	 * @param	$aNode	The Framework_DB_DaoNode object to compare.
	 * @return	boolean
	 */
	public function equals( Framework_DB_DaoNode $aNode ) {

		$_output = false ;

		foreach( $this->_schema['fields'] as $fieldName ) {
			if( $this->$fieldName == $aNode->$fieldName ) {
				$_output = true ;
			} else {
				$_output = false ;
				break ;
			}
		}

		return $_output ;
	}
	
	public function hasAttribute($attribute) {
		return property_exists($this, $attribute) || array_key_exists($attribute, $this->_attributes);
	}

	public function __destruct() {
		parent::__destruct() ;
	}

}

?>
